﻿using System;
using System.Linq;
using System.Xml.Linq;

public static class XExtensions
{
    public static string GetAbsoluteXPath(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        Func<XElement, string> relativeXPath = e =>
        {
            int index = e.IndexPosition();

            var currentNamespace = e.Name.Namespace;

            string name;

            if (currentNamespace == null)
            {
                name = e.Name.LocalName;
            }
            else
            {
                string namespacePrefix = e.GetPrefixOfNamespace(currentNamespace);
                name = namespacePrefix + ":" + e.Name.LocalName;
            }

            return (index == -1) ? "/" + name : string.Format("/{0}[{1}]", name, index.ToString());
        };

        var ancestors = from e in element.Ancestors()
                        select relativeXPath(e);

        return string.Concat(ancestors.Reverse().ToArray()) + relativeXPath(element);
    }

    public static string GetAbsoluteXPath(this XAttribute attribute)
    {
        if (attribute == null)
        {
            throw new ArgumentNullException("element");
        }

        Func<XElement, string> relativeXPath = e =>
        {
            int index = e.IndexPosition();

            var currentNamespace = e.Name.Namespace;

            string name;

            if (currentNamespace == null)
            {
                name = e.Name.LocalName;
            }
            else
            {
                string namespacePrefix = e.GetPrefixOfNamespace(currentNamespace);
                name = namespacePrefix + ":" + e.Name.LocalName;
            }

            return (index == -1) ? "/" + name : string.Format("/{0}[{1}]", name, index.ToString());
        };

        var ancestors = from e in attribute.Parent.Ancestors()
                        select relativeXPath(e.Parent);

        return string.Concat(ancestors.Reverse().ToArray()) + relativeXPath(attribute.Parent);
    }

    public static int IndexPosition(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        if (element.Parent == null)
        {
            return -1;
        }

        int i = 1; // Indexes for nodes start at 1, not 0

        foreach (var sibling in element.Parent.Elements(element.Name))
        {
            if (sibling == element)
            {
                return i;
            }

            i++;
        }

        throw new InvalidOperationException("element has been removed from its parent.");
    }

    public static int IndexPosition(this XAttribute attribute)
    {
        if (attribute == null)
        {
            throw new ArgumentNullException("element");
        }

        if (attribute.Parent == null)
        {
            return -1;
        }

        int i = 1; // Indexes for nodes start at 1, not 0

        foreach (var sibling in attribute.Parent.Attributes(attribute.Name))
        {
            if (sibling == attribute)
            {
                return i;
            }

            i++;
        }

        throw new InvalidOperationException("element has been removed from its parent.");
    }
}